package com.smp.support.shared; /**
 * Alipay.com Inc.
 * Copyright (c) 2004-2020 All Rights Reserved.
 */

import com.smp.domain.BaseInputDomain;
import com.smp.domain.BaseOutputDomain;
import com.smp.domain.SmpBaseResult;
import com.smp.domain.ServiceMonitorModel;
import com.smp.enums.ExpoErrorCodeEnum;
import com.smp.exception.ExpoAppException;
import com.smp.util.ExpoAppLogUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

/**
 * 服务模板，当前模板的主方法是静态方法，后续随着业务复杂，模板可能承载或依赖更多能力时，需要相应做演进；
 *
 * @author zhulang.jy
 * @version : SmpServiceTemplate.java, v 0.1 2020年10月14日 15:08 zhulang.jy Exp $
 */
public class SmpServiceTemplate {

    /**
     * 服务监控日志
     */
    private static final Logger MONITOR_LOGGER = LoggerFactory.getLogger(LoggerNames.EXPO_SERVICE_MONITOR);

    /**
     * 服务详细日志
     */
    private static final Logger DETAIL_LOGGER = LoggerFactory.getLogger(LoggerNames.EXPO_SERVICE_DETAIL);

    /**
     * 请求成功结果码
     */
    private static final String RESULT_CODE_SUCCESS = "SUCCESS";

    private SmpServiceTemplate() {
    }

    /**
     * 服务模板主方法，直接or间接依赖领域服务，编排业务服务逻辑
     */
    public static <T extends BaseInputDomain, R extends BaseOutputDomain> void execute(ServiceContext<T, R> context, SmpServiceCallback<T, R> callback) {
        try {
            context.setOutput(callback.buildOutputDomain());
            // 日志模型初始化
            ServiceMonitorModel monitor = context.getMonitorModel();
            monitor.setStartTime(System.currentTimeMillis());

            // 服务开始执行 因为接口传来"{}"日志会报错，暂时注释 20201127
            ExpoAppLogUtils.info(DETAIL_LOGGER,
                    "Service start: {0},  Request key parameters: {1}", context
                            .getServiceType().getDesc(), context
                            .getOriginInput());

            // 业务相关请求前置检查
            callback.preCheck();

            // 业务相关场景初始化
            callback.init();

            // 构建业务相关的请求与相应的领域对象
            context.setInput(callback.buildInputDomain());

            // 处理逻辑：业务processor拆分 or 直接简单实现
            callback.doProcess(context);

            // 执行到此处，代表已经执行成功，失败会在外部加异常捕捉里处理，并设置状态为失败
            context.getOutput().setSuccess(true);
            monitor.setResult(ServiceMonitorModel.Result.SUCCESS);
        } catch (ExpoAppException e) {
            // 打印并处理业务异常
            ExpoAppLogUtils.warn(e, DETAIL_LOGGER,
                    "Execute failed with business exception: {0}, {1}, {2}", context.getServiceType()
                            .getDesc(), context.getMonitorModel().getRequestShortBizInfo(), e.getErrorCodeEnum());
            handleException(e, context);
        } catch (Exception e) {
            // 打印并处理系统异常
            ExpoAppLogUtils.error(e, DETAIL_LOGGER,
                    "Execute failed with system exception: {0}, {1}", context.getServiceType()
                            .getDesc(), context.getMonitorModel()
                            .getRequestShortBizInfo());
            ExpoAppException bizException = new ExpoAppException(ExpoErrorCodeEnum.SYSTEM_EXCEPTION, e);
            handleException(bizException, context);
        } finally {
            // 构建最终业务结果
            Object resultObj = callback.buildResult(context.getOutput());
            context.getMonitorModel().recordElapseTime();

            // 打印摘要日志和详情日志 因为接口传来"{}"日志会报错，暂时注释 20201127
            ExpoAppLogUtils.info(MONITOR_LOGGER, "MonitorModel log:{0}", context.getMonitorModel().showLog());
            ExpoAppLogUtils.info(DETAIL_LOGGER, "Execute finished:{0}, result information:{1}",
                    context.getServiceType().getDesc(), resultObj);
        }
    }

    /**
     * 处理异常情况
     * SchedulerTask
     *
     * @param expoAppException expo业务异常
     * @param context          服务上下文
     */
    private static void handleException(ExpoAppException expoAppException, ServiceContext context) {
        context.getOutput().setSuccess(false);
        context.getOutput().setErrorCode(expoAppException.getErrorCodeEnum());
        context.getOutput().setErrorMsg(expoAppException.getMessage());
        context.getMonitorModel().setResult(ServiceMonitorModel.Result.FAILED);
        context.getMonitorModel().setResultCode(expoAppException.getErrorCodeEnum());
    }

    /**
     * 根据领域层结果构建业务层结果
     *
     * @param responseDomain 领域层结果
     * @param result         业务层结果
     */
    public static void fillResult(BaseOutputDomain responseDomain, SmpBaseResult result) {

        result.setTraceId(MDC.get(ApplicationConstants.MDC_TRACE_KEY));
        result.setSuccess(responseDomain.isSuccess());
        if (responseDomain.getErrorCode() != null) {
            result.setResultCode(responseDomain.getErrorCode().getErrorCode());
        } else {
            result.setResultCode(RESULT_CODE_SUCCESS);
        }
        result.setErrorMsg(responseDomain.getErrorMsg());
    }

}